package edu.amm.neur;

import java.util.List;

import edu.amm.utils.AlgebraUtils;
import edu.amm.utils.RandomUtils;

/**
 * Нейронная сеть на основе радиально-базисных функций активации.
 * 
 * @author Иванов Илья
 * @since 2014.03.10
 *
 */
public class RBFNetwork {
	
	/** Число входов сети. */
	private int n;
	
	/** Количество нейронов первого слоя - число эталонных векторов. */
	private int l;
	
	/** Количество нейронов второго слоя - число выходов сети. */
	private int m;
	
	/** Матрица весов первого слоя. Размерность l x n. */
	private double[][] c;
	
	/** Матрица весов второго слоя. Размерность l x m. */
	private double[][] w;
	
	// Границы для генерации w на этапе инициализации.
	private static final double W_MIN = -1E-3;
	private static final double W_MAX = 1E+3;
	
	// Параметры активационных функций нейронов первого слоя.
	
	/** Центры радиальных элементов. */
	private double[] r;
	
	// Границы для генерации r на этапе инициализации.
	private static final double R_MIN = -1;
	private static final double R_MAX = 1;
	
	/**
	 * Минимальное значение изменения вектора r по норме, при достижении которого итерационный процесс расчёта вектора
	 * r прекращается.
	 */
	private static final double R_MIN_VAR = 1E-3;
	
	// Параметр темпа обучения.
	private double alpha;
	private static final double INIT_ALPHA = 0.9;
	private static final double DECR_ALPHA = 0.9;
	
	// Максимальное расстояние между центрами.
	private double d;
	private static final double INIT_D = 1;
	private static final double DECR_D = 0.9;
	
	/** Отклонения радиальных элементов. */
	private double[] sigma;
	
	/**
	 * Создаёт сеть.
	 * 
	 * @param n Количество входов сети.
	 * @param l Количество нейронов первого слоя - число эталонных векторов.
	 * @param m Количество нейронов второго слоя.
	 */
	public RBFNetwork(int n, int l, int m) {
		if (n < 1 || l < 1 || m < 1) {
			throw new RuntimeException("Некорректные параметры размерности сети");
		}
		
		this.n = n;
		this.l = l;
		this.m = m;
		
		c = new double[l][n];
		w = new double[l][m];
		r = new double[l];
		sigma = new double[l];
	}
	
	/**
	 * Инициализирует процесс обучения сети.
	 * 
	 * @param c Матрица весов первого слоя размерности l x n. Строками матрицы являются эталонные векторы сети.
	 */
	public void initialize(double[][] c) {
		if (!AlgebraUtils.checkMatrix(c) || c.length != l || c[0].length != n) {
			throw new RuntimeException("Некорректная матрица весов первого слоя");
		}
		
		AlgebraUtils.copyMatrix(c, this.c);
		
		// Нормируем матрицу весов первого слоя по строкам.
		AlgebraUtils.normalizeByRows(this.c);
		
		// Генерируем случайным образом начальные значения центров.
		for (int i = 0; i < r.length; i++) {
			r[i] = RandomUtils.generateFromSegment(R_MIN, R_MAX);
		}
		
		// Генерируем случайным образом начальные значения весовых коэффициентов второго слоя.
		for (int i = 0; i < w.length; i++) {
			for (int j = 0; j < w[i].length; j++) {
				w[i][j] = RandomUtils.generateFromSegment(W_MIN, W_MAX);
			}
		}
		
		// Нормируем матрицу весов второго слоя по столбцам.
		AlgebraUtils.normalizeByColumns(w);
		
		// Инициализируем темп обучения.
		alpha = INIT_ALPHA;
		// Инициализируем максимальное расстояние между центрами.
		d = INIT_D;
	}
	
	/**
	 * Обучает сеть на заданном множестве обучающих пар.
	 * 
	 * @param pairs Множество обучающих пар.
	 */
	public void train(List<Pair> pairs) {
		checkPairs(pairs);
		
		// Вычисляем расстояние от входного вектора до всех центров сети.
		double[] dr = new double[l];
	}
	
	/**
	 * Проверяет корректность множества обучающих пар. Генерирует {@link RuntimeException}, если множество некорректно.
	 * 
	 * @param pairs Множество обучающих пар для проверки.
	 */
	private void checkPairs(List<Pair> pairs) {
		if (pairs == null || pairs.isEmpty()) {
			throw new RuntimeException("Множество обучающих пар некорректно");
		}
		
		for (Pair pair : pairs) {
			if (pair == null || pair.getX() == null || pair.getX().length != n ||
				pair.getY() == null || pair.getY().length != m) {
				throw new RuntimeException("Некорректная обучающая пара");
			}
		}
	}
}
